package org.xwalk.core;


import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * This class represents an extension and could be implemented by callers.
 */

public abstract class XWalkExtension   {

    private ArrayList<Object> constructorTypes;
    private ArrayList<Object> constructorParams;
    private ReflectMethod postWrapperMethod;


    private XWalkCoreWrapper coreWrapper;
    private Object bridge;

    protected Object getBridge() {
        return bridge;
    }


    /**
     * Constructor with name and javascript API.
     * @param name  the exposed namespace.
     * @param jsApi the string of javascript API.
     * @since 2.1
     */
    public XWalkExtension(String name, String jsApi) {
        constructorTypes = new ArrayList<Object>();
        constructorTypes.add(String.class);
        constructorTypes.add(String.class);

        constructorParams = new ArrayList<Object>();
        constructorParams.add(name);
        constructorParams.add(jsApi);

        reflectionInit();
    }

    /**
     * Constructor with name, javascript API and entry points.
     * @param name the exposed namespace.
     * @param jsApi the string of javascript API.
     * @param entryPoints Entry points are used when the extension needs to
     *                    have objects outside the namespace that is
     *                    implicitly created using its name.
     * @since 2.1
     */
    public XWalkExtension(String name, String jsApi, String[] entryPoints) {
        constructorTypes = new ArrayList<Object>();
        constructorTypes.add(String.class);
        constructorTypes.add(String.class);
        constructorTypes.add(String[].class);

        constructorParams = new ArrayList<Object>();
        constructorParams.add(name);
        constructorParams.add(jsApi);
        constructorParams.add(entryPoints);

        reflectionInit();
    }

    /**
     * Send message to an instance.
     * @param instanceID the id of instance.
     * @param message the message.
     * @since 2.1
     */
    public void postMessage(int instanceID, String message) {
        try {
            postMessageintStringMethod.invoke(instanceID, message);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod postMessageintStringMethod = new ReflectMethod(null, "postMessage");

    /**
     * Send binary message to an instance.
     * @param instanceID the id of instance.
     * @param message the binary message.
     * @since 6.0
     */
    public void postBinaryMessage(int instanceID, byte[] message) {
        try {
            postBinaryMessageintbyteArrayMethod.invoke(instanceID, message);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod postBinaryMessageintbyteArrayMethod = new ReflectMethod(null, "postBinaryMessage");

    /**
     * Broadcast message to all extension instances.
     * @param message the message.
     * @since 2.1
     */
    public void broadcastMessage(String message) {
        try {
            broadcastMessageStringMethod.invoke(message);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod broadcastMessageStringMethod = new ReflectMethod(null, "broadcastMessage");

    /**
     * Notify the extension that an instance is created.
     * @param instanceID the id of instance.
     * @since 15.45
     */
    public void onInstanceCreated(int instanceID) {
        try {
            onInstanceCreatedintMethod.invoke(instanceID);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onInstanceCreatedintMethod = new ReflectMethod(null, "onInstanceCreated");

    /**
     * Notify the extension that an instance is destroyed.
     * @param instanceID the id of instance.
     * @since 15.45
     */
    public void onInstanceDestroyed(int instanceID) {
        try {
            onInstanceDestroyedintMethod.invoke(instanceID);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onInstanceDestroyedintMethod = new ReflectMethod(null, "onInstanceDestroyed");

    /**
     * Notify the extension that the async binary message is received.
     * @param instanceID the id of instance.
     * @param message the received binar message.
     * @since 6.0
     */
    public void onBinaryMessage(int instanceID, byte[] message) {
        try {
            onBinaryMessageintbyteArrayMethod.invoke(instanceID, message);
        } catch (UnsupportedOperationException e) {
            if (coreWrapper == null) {
                throw new RuntimeException("Crosswalk's APIs are not ready yet");
            } else {
                XWalkCoreWrapper.handleRuntimeError(e);
            }
        }
        
    }

    private ReflectMethod onBinaryMessageintbyteArrayMethod = new ReflectMethod(null, "onBinaryMessage");

    /**
     * Notify the extension that the async message is received.
     * @param instanceID the id of instance.
     * @param message the received message.
     * @since 2.1
     */
    public abstract void onMessage(int instanceID, String message);

    /**
     * Notify the extension that the sync message is received.
     * @param instanceID the id of instance.
     * @param message the received message.
     * @since 2.1
     */
    public abstract String onSyncMessage(int instanceID, String message);


    void reflectionInit() {
        XWalkCoreWrapper.initEmbeddedMode();

        coreWrapper = XWalkCoreWrapper.getInstance();
        if (coreWrapper == null) {
            XWalkCoreWrapper.reserveReflectObject(this);
            return;
        }

        int length = constructorTypes.size();
        Class<?>[] paramTypes = new Class<?>[length+1];
        for (int i = 0; i < length; ++i) {
            Object type = constructorTypes.get(i);
            if (type instanceof String) {
                paramTypes[i] = coreWrapper.getBridgeClass((String) type);
                constructorParams.set(i, coreWrapper.getBridgeObject(constructorParams.get(i)));
            } else if (type instanceof Class<?>) {
                paramTypes[i] = (Class<?>) type;
            } else {
                assert(false);
            }
        }

        paramTypes[length] = Object.class;
        constructorParams.add(this);

        ReflectConstructor constructor = new ReflectConstructor(
                coreWrapper.getBridgeClass("XWalkExtensionBridge"), paramTypes);
        try {
            bridge = constructor.newInstance(constructorParams.toArray());
        } catch (UnsupportedOperationException e) {
            return;
        }

        if (postWrapperMethod != null) postWrapperMethod.invoke();

        postMessageintStringMethod.init(bridge, null,
                "postMessageSuper", int.class, String.class);
        postBinaryMessageintbyteArrayMethod.init(bridge, null,
                "postBinaryMessageSuper", int.class, byte[].class);
        broadcastMessageStringMethod.init(bridge, null,
                "broadcastMessageSuper", String.class);
        onInstanceCreatedintMethod.init(bridge, null,
                "onInstanceCreatedSuper", int.class);
        onInstanceDestroyedintMethod.init(bridge, null,
                "onInstanceDestroyedSuper", int.class);
        onBinaryMessageintbyteArrayMethod.init(bridge, null,
                "onBinaryMessageSuper", int.class, byte[].class);
    }

}
